package org.xiaodongwei.jvm.op;

import org.xiaodongwei.jvm.exe.JVMStack;
import org.xiaodongwei.jvm.exe.JVMStackFrame;
import org.xiaodongwei.jvm.load.BootstrapClassLoader;
import tech.medivh.classpy.classfile.ClassFile;
import tech.medivh.classpy.classfile.MethodInfo;
import tech.medivh.classpy.classfile.bytecode.Instruction;
import tech.medivh.classpy.classfile.bytecode.InvokeStatic;
import tech.medivh.classpy.classfile.constant.ConstantMethodrefInfo;
import tech.medivh.classpy.classfile.constant.ConstantPool;
import tech.medivh.classpy.classfile.jvm.Opcode;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

public class InvokeStaticOpCodeExecute extends OpCodeExecute {

    private BootstrapClassLoader classLoader;

    public InvokeStaticOpCodeExecute(JVMStack jvmStack, ConstantPool constantPool, BootstrapClassLoader classLoader) {
        this.opCode = Opcode.invokestatic;
        this.jvmStack = jvmStack;
        this.constantPool = constantPool;
        this.classLoader = classLoader;
    }

    @Override
    public void execute(Instruction instruction) {
        InvokeStatic invokeStatic = (InvokeStatic) instruction;
        ConstantMethodrefInfo methodInfo = invokeStatic.getMethodInfo(constantPool);
        String className = methodInfo.className(constantPool);
        String methodName = methodInfo.methodName(constantPool);
        List<String> params = methodInfo.paramClassName(constantPool);
        try {
            if (className.contains("java")) {
                Class<?> aClass = Class.forName(className);
                Method declaredMethod = aClass.getDeclaredMethod(methodName,
                        params.stream().map(this::nameToClass).toArray(Class[]::new));
                Object[] args = new Object[params.size()];
                for (int index = args.length - 1; index >= 0; index--) {
                    args[index] = jvmStack.peek().getOperateStack().pop();
                }
                Object result = declaredMethod.invoke(null, args);
                if (!methodInfo.isVoid(constantPool)) {
                    jvmStack.peek().pushToOperateStack(result);
                }
                return;
            }
            ClassFile classFile = classLoader.loadClass(className);
            MethodInfo finalMethodInfo = classFile.getMethods(methodName).get(0);
            Object[] args = new Object[params.size()];
            for (int index = args.length - 1; index >= 0; index--) {
                args[index] = jvmStack.peek().getOperateStack().pop();
            }
            JVMStackFrame stackFrame = new JVMStackFrame(finalMethodInfo, classFile.getConstantPool(), args);
            jvmStack.push(stackFrame);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private Class<?> nameToClass(String className) {
        if (className.equals("int")) {
            return int.class;
        } else if (className.equals("float")) {
            return float.class;
        }
        try {
            return Class.forName(className);
        } catch (Exception e) {
            return null;
        }
    }
}
